Python爬虫之JavaScript动态渲染页面爬取(Pyppeteer的使用)

您所在的位置:网站首页 Js 加载网页内容 Python爬虫之JavaScript动态渲染页面爬取(Pyppeteer的使用)

Python爬虫之JavaScript动态渲染页面爬取(Pyppeteer的使用)

2024-07-12 18:43| 来源: 网络整理| 查看: 265

JavaScript动态渲染页面爬取之Pyppeteer的使用

JavaScript动态渲染的页面不止Ajax一种。例如有些页面的分页部分由JavaScript生成,非原始HTML代码。

为了解决这些问题,我们可以直接模拟浏览器运行,然后爬取数据,这样就可以实现所见即所爬。

Python提供了许多模拟浏览器运行的库,例如Selenium、Splash、Pyppeteer、Playwright等。

一、Pyppeteer的使用

在很多情况下,Ajax请求的接口含有加密参数,例如token、sign等。由于请求Ajax接口时必须加上token参数,因此得深入分析并找到token参数的构造逻辑,难以模拟请求。

因此,模拟浏览器的运行,爬取数据即可解决。

1、Pyppeteer介绍

Pyppeteer依赖Chromium浏览器运行的。如果第一次运行Pyppeteer的时候,没有安装Chromium浏览器,程序会自动帮我们自动安装和配置好,另外,Pyppeteer是基于Python的新特性asnc实现的,所以它的一些操作执行也支持异步方式。

2、安装 pip3 install Pyppeteer 3、快速上手 import asyncio from pyppeteer import launch from pyquery import PyQuery as pq async def main(): browser = await launch() # 新建一个browser对象。相当于启动浏览器 page = await browser.newPage() #新建一个page对象并赋值给page变量,这相当于在浏览器中新建了一个选项卡,但还未访问任何页面。 await page.goto('https://spa2.scrape.center/') # 调用page的goto方法,相当于在浏览器中输入page方法参数中的URL,浏览器加载对应的页面 await page.waitForSelector('.item .name') #调用page的waitForSelector方法,传入选择器,页面就会等待选择器对应的节点信息加载出来后立即返回,否则等待直到超时。 doc = pq(await page.content()) # 页面加载出来后,调用content方法,获取当前浏览器的源代码,这就是JavaScript渲染后的结果 names = [item.text() for item in doc('.item .name').items()] # 使用pyquery解析页面,提取信息 print('Names:',names) await browser.close() asyncio.get_event_loop().run_until_complete(main()) import asyncio from pyppeteer import launch width, height = 1366,768 async def main(): browser = await launch() page = await browser.newPage() await page.setViewport({'width':width,'height':height}) # 设置页面窗口的大小 await page.goto('https://spa2.scrape.center/') await page.waitForSelector('.item .name') await asyncio.sleep(2) await page.screenshot(path='example.png') # 保存页面截图 dimensions = await page.evaluate('''() => { # 执行JavaScript语句并返回对应的数据 return { width:document.documentElement.clientWidth, height:document.documentElement.clientHeight, deviceScaleFactor:window.devicePixelRatio, } }''') print(dimensions) await browser.close() asyncio.get_event_loop().run_until_complete(main())

在screenshot方法里,通过path参数用于传入页面截图的保存路径,另外还可以指定截图的格式type、清晰度quality、是否全屏fullPage和裁切clip等参数。

总之、利用pyppeteer可以控制浏览器执行几乎所有想实现的操作和功能。

4、launch方法

使用pyppeteer的第一步就是启动浏览器。调用launch方法即可。

launch方法的API:

pyppeteer,launcher.launch(options:dic = None,**kwargs) -> pyppeteer.browser.Browser # 观察源码可以发现,这是一个async修饰的方法,所以在调用的时候要加await

launch方法的参数:

ignoreHTTPSErrors(bool):是否忽略HTTPS的错误,默认是False。headless(bool):是否启用无头模式,即无界面模式。如果devtools参数是True,该参数会被设置为False,否则为True,即默认开启无界面模式。executablePath(str):可执行文件的路径。指定该参数之后就不需要使用默认的Chromium浏览器了,可以指定已有的Chrome或Chromium。slowMo(int|float):通过传入指定的时间,可以减缓Pyppeteer的一些模拟操作。args(List|float):在执行过程中可以传入额外参数。ignoreDefaultArgs(bool):是否忽略Pyppeteer的默认参数。如果使用这个参数,那么最好通过args设置一些参数,否则可能会出现一些意想不到的问题。这个参数相对比较危险。handleSIGINT(bool):是否响应SIGINT信号,也就是是否可以使用Ctrl+C终止浏览器程序,默认为True。handleSIGTERM(bool):是否响应SIGTERM信号(一般是KILL命令),默认是True。handleSIGHUP(bool):是否响应SIGHUP信号,即挂起信号,例如终端退出操作,默认是True。dumpio(bool):是否将Pyppeteer的输出内容传给process,stdout对象和process,stderr对象,默认是False。userDataDir(str):用户数据文件夹,可以保留一些个性化配置和操作记录。env(dict):环境变量,可以传入字典形式的数据。devtools(bool):是否自动为每一个页面开启调试工具默认是False。如果这个参数设置为True,那么headless参数就会无效,会被强制设置为False。logLevel(int|str):日志级别,默认和root logger对象的级别相同。autoClose(bool):当一些命令执行完之后,是否自动关闭浏览器,默认是True。loop(asyncio.AbstractEventLoop):事件循环对象。 5、无头模式 import asyncio from pyppeteer import launch async def main(): await launch(headless=False) # 设为False,启动时就能看见界面了 await asyncio.sleep(100) asyncio.get_event_loop().run_until_complete(main()) 6、调试模式

在写爬虫的时候会经常需要分析网页结构和网络请求,所以开启调试模式是非常有必要的。

import asyncio from pyppeteer import launch async def main(): browser = await launch(devtools=True) page = await browser.newPage() await page.goto('https://www.baidu.com') await asyncio.sleep(100) asyncio.get_event_loop().run_until_complete(main())

刚才说过,如果devtools参数设置为True,无头模式就会关闭,界面始终会显示出来。

7、禁用提示条

可以看到在第5点上有一个提示“Chrome正受到自动测试软件的控制”,用args参数去除。

browser = await launch(headless=False,args=['--disable-infobars']) 8、防止检测

刚刚只是提示关闭了,有些网站还是能检测到Webdriver属性。

Pyppeteer的Page对象有一个叫做evaluateOnNewDocument方法,意思是在每次加载网页的时候执行某条语句,这里可以利用它执行隐藏Webdriver属性的命令:

import asyncio from pyppeteer import launch async def main(): browser = await launch(headless=False,args=['--disable-infobars']) page = await browser.newPage() await page.evaluateOnNewDocument('Object.defineProperty(navigator,"webdriver",{get:() => undefined})') await page.goto('https://antispider1.scrape.center/') await asyncio.sleep(100) asyncio.get_event_loop().run_until_complete(main())

可以看到整个页面成功加载出来了,绕过了对Webdriver属性的检测

9、页面大小的设置

在上述,可以发现页面的显示BUG,整个浏览器的窗口要比显示内容的窗口大,这个情况并非每个页面都会出现。调用Page对象的setViewport方法可以设置窗口大小:

import asyncio from pyppeteer import launch width,height = 1366,768 async def main(): browser = await launch(headless=False,args=['--disable-infobars',f'--window-size={width},{height}']) page = await browser.newPage() await page.evaluateOnNewDocument('Object.defineProperty(navigator,"webdriver",{get:() => undefined})') await page.goto('https://antispider1.scrape.center/') await asyncio.sleep(100) asyncio.get_event_loop().run_until_complete(main()) 10、用户数据持久化

我们看到,每次打开pyppeteer的时候,都是一个新的空白浏览器。如果网页需要登录,那得反复登录!!

设置用户目录即可解决:

import asyncio from pyppeteer import launch async def main(): browser = await launch(headless=False,userDataDir='./userdata',args=['--disable-infobars']) page = await browser.newPage() await page.goto('https://taobao.com') await asyncio.sleep(100) asyncio.get_event_loop().run_until_complete(main())

这里将userData属性的值设置为了./userdata,即当前目录的userdata文件夹。关于这个文件夹,具体看https://chromium.googlesource.com/chromium/sec/+/master/docs/user_data_dir.md

以上是launch方法及其对应参数的配置。

11、Browser

我们了解launch方法,它的返回值是一个Browser对象,即浏览器对象,我们通常会赋值给browser变量(其实就是Browser类的一个实例)

Browser类的定义:

class pyppeteer.browser.Browser(connection:pyppeteer.connection.Connection,contextIds:List[str],ignoreHTTPSErrors:bool,setDefaultViewport:bool,process:Optional[subprocess.Popen] = None,closeCallback:Callable[[],Awaitable[None]] = None.**kwargs) 12、开启无痕模式

可以通过createIncognitoBrowserConText方法开启无痕模式:

import asyncio from pyppeteer import launch width,height = 1200,768 async def main(): browser = await launch(headless=False,args=['--disable-infobars',f'--window-size={width},{height}']) context = await browser.createIncognitoBrowserContext() page = await context.newPage() await page.setViewport({'width':width,'height':height}) await page.goto('https://www.baidu.com') await asyncio.sleep(100) asyncio.get_event_loop().run_until_complete(main()) 13、关闭

close方法关闭浏览器:

async def main(): browser = await launch() page = await browser.newPage() await page.goto('https://spa2.scrape.center/') await browser.close() asyncio.get_event_loop().run_until_complete(main()) 14、Page

Page即页面,对应一个网页、一个选项卡。

14.1、选择器

Page对象内置了很多用于选取节点的选择器方法,例如J方法,给它传入一个选择器,就能返回匹配到的第一个节点,等价于querySelector方法;又如JJ方法,给它传入选择器,会返回符合选择器的所有节点组成的列表,等价于queySelectorAll方法。

import asyncio from pyppeteer import launch async def main(): browser = await launch() page = await browser.newPage() await page.goto('https://spa2.scrape.center/') await page.waitForSelector('.item .name') j_result1 = await page.J('.item .name') j_result2 = await page.querySelector('.item .name') jj_result1 = await page.JJ('.item .name') jj_result2 = await page.querySelectorAll('.item .name') print('J Result1:',j_result1) print('J Result2:',j_result2) print('JJ Result1:',jj_result1) print('JJ Result2:',jj_result2) await browser.close() asyncio.get_event_loop().run_until_complete(main()) 14.2、选项卡操作

新建选项卡后,先调用pages方法获取所有打开的页面,然后选择一个页面调用其bringToFront方法即可切换页面。

import asyncio from pyppeteer import launch async def main(): browser = await launch(headless=False) page = await browser.newPage() await page.goto('https://www.baidu.com') page = await browser.newPage() await page.goto('https://www.bing.com') pages = await browser.pages() print('Pages:',pages) page1 = pages[1] await page1.bringToFront() await asyncio.sleep(100) asyncio.get_event_loop().run_until_complete(main())

这里先启动了Pyppeteer,然后调用了newPage方法新建了两个选项卡,并访问了两个网站。

14.3、页面操作

一定要有对应的方法来控制一个页面的加载、前进、后退、关闭和保存等行为:

import asyncio from pyppeteer import launch async def main(): browser = await launch(headless=False) page = await browser.newPage() await page.goto('https://dynamic1.scrape.cuiqingcai.com/') await page.goto('https://spa2.scrape.cemter/') # 后退 await page.goBack() # 前进 await page.goForward() # 刷新 await page.reload() # 保存PDF await page.pdf() # 截图 await page.screenshot() # 设置页面HTML await page.setContent() # 设置User-Agent await page.setUserAgent() # 设置Headers await page.setExtraHTTPHeaders(headers={}) # 关闭 await page.close() await browser.close() asyncio.get_event_loop().run_until_complete(main()) 14.4、点击

Pyppeteer同样可以模拟点击,调用click方法即可。

import asyncio from pyppeteer import launch from pyquery import PyQuery as pq async def main(): browser = await launch() page = await browser.newPage() await page.goto('https://spa2.scrape.center/') await page.waitForSelector('.item .name') await page.click('.item .name',options={ 'button':'right', 'clickCount':1, 'delay':3000, }) await browser.close() asyncio.get_event_loop().run_until_complete(main()) button:鼠标按钮,取值有left、middle、right。clickCount:点击次数,取值有left,right,middle。delay:延迟点击。 14.5、输入文本

使用type方法可以输入文本

import asyncio from pyppeteer import launch async def main(): browser = await launch(headless=False) page = await browser.newPage() await page.goto('https://www.taobao.com') await page.type('#q','iPad') await asyncio.sleep(10) await browser.close() asyncio.get_event_loop().run_until_complete(main()) 14.6、获取信息 import asyncio from pyppeteer import launch from pyquery import PyQuery as pq async def main(): browser = await launch(headless=False) page = await browser.newPage() await page.goto('https://spa2.scrape.center/') print('HTML:',await page.content()) print('Cookies:',await page.cookies()) await browser.close() asyncio.get_event_loop().run_until_complete(main()) 14.7、执行

可以用evaluate执行JavaScript语句

import asyncio from pyppeteer import launch from pyquery import PyQuery as pq width, height = 1366,768 async def main(): browser = await launch() page = await browser.newPage() await page.setViewport({'width':width,'height':height}) await page.goto('https://spa2.scrape.center/') await page.waitForSelector('.item .name') await asyncio.sleep(2) await page.screenshot(path='example.png') dimensions = await page.evaluate('''() => { return { width:document.documentElement.clientWidth, height:document.documentElement.clientHeight, deviceScaleFactor:window.devicePixelRatio, } }''') print(dimensions) await browser.close() asyncio.get_event_loop().run_until_complete(main()) 14.8、延迟等待

在本节开始的时候,我们演示了waitForSelector的用法,它可以让页面等待某些符合条件的节点加载出来再返回结果。还有其他等待方法:

waitForFunction:等待某个JavaScript方法执行完毕或返回结果waitForNavigation:等待页面跳转,如果没加载出来就报错waitForRequest:等待某个特定的请求发出waitForResponse:等待某个特定请求对应的响应waitFor:通用的等待方法waitForXPath:等待符合XPath的节点加载出来。 二、pyppeteer爬取实战 1、爬取目标

电影网站:https://spa2.scrape.center/

2、工作 遍历每一页列表页,获取每部电影详情页的URL爬取每部电影的详情页,提取电影的名称、评分、类别、封面、简介等信息。将爬取的数据存储至数据库 3、准备工作 Python与pyppeteer库 4、爬取列表页

准备工作:

# 准备工作 import logging logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s : %(message)s') # 定义日志配置 INDEX_URL = 'https://spa2.scrape.center/page/{page}' TIEMEOUT = 10 TOTAL_PAGE = 10 WINDOW_WIDTH, WINDOW_HEIGHT = 1366, 768 # 浏览器的宽和高 HEADLESS = False # 指定是否启用无头模式,False代表会弹窗

定义初始化pyppeteer的方法:

# 初始化pyppeteer方法 from pyppeteer import launch browser, tab = None, None # 声明变量,前者代表浏览器对象,后者代表新建的页面选项卡。 async def init(): global browser, tab # 设置为全局变量,能够在其它方法里调用 browser = await launch(headless=HEADLESS, args=['--disabled-infobars', f'--window-size={WINDOW_WIDTH},{WINDOW_HEIGHT}']) # args参数指定隐藏提示条和设置浏览器窗口的宽高 tab = await browser.newPage() await tab.setViewport({'width': WINDOW_WIDTH, 'height': WINDOW_HEIGHT})

定义一个通用的爬取方法:

# 定义一个通用的爬取方法 from pyppeteer.errors import TimeoutError async def scrape_page(url,selector): # 定义两个参数,url代表要爬取的页面的URL,使用goto方法调用此参数即可访问对应页面;另一个是selector,即等待渲染出的节点对应的CSS选择器。 logging.info('scraping %s',url) try: await tab.goto(url) await tab.waitForSelector(selector,options={ 'timeout':TIEMEOUT * 1000 }) # waitForSelector方法等待selector选择器匹配的节点加载出来,通过option指定最长等待时间 except TimeoutError: # 超时则报出异常 logging.error('error occurred while scraping %s',url,exc_info=True)

实现爬取列表页的方法:

# 列表页的爬取 async def scrape_index(page): # 接受参数page,代表要爬取的页面的页码 url = INDEX_URL.format(page=page) # 通过format方法构造出列表页的URL await scrape_page(url,'.item .name') # 同时传入选择器,.item .name是列表页中电影的名称

在定义一个解析列表页的方法,用来提取每部电影的详情页URL:

# 解析列表页 async def parse_index(): return await tab.querySelectorAllEval('.item .name','nodes => nodes.map(node => node.href)') # 这里调用了querySelectorAllEval方法,接受两个参数:一是selector,代表选择器;另一个是pageFunction,代表要执行的JavaScript方法。这个方法的作用是找出和选择器匹配的节点,然后根据pageFunction定义的逻辑从这些节点中抽取对应的结果并返回。 # 我们给参数selector传入了电影名称。由于和选择器相匹配的节点有多个,所以给pageFunction参数输入的JavaScript方法就是nodes,其返回值是调用map方法得到node,然后调用node的href属性得到的超链接。这样querSelectorAllEval的返回结果就是当前列表页中的所有电影的详情页的URL组成的列表。

串联调用刚刚实现的方法:

import asyncio async def main(): await init() # 首先调用init方法 try: for page in range(1,TOTAL_PAGE + 1): # 遍历所有页码 await scrape_index(page) # 爬取每一个列表页 detail_urls = await parse_index() # 从列表页提取每个URL logging.info('detail_urls %s',detail_data) # 输出 finally: await browser.close() if __name__ == '__main__': asyncio.get_event_loop().run_until_complete(main()) 5、爬取详情页

定义爬取详情页的方法:

async def scrape_detail(url): await scrape_page(url,'h2') # 直接调用scrape_page方法,传入详情页url和选择器即可,这里h2代表电影名称。

提取详情页里的信息的方法:

# 提取详情页里面的信息 async def parse_detail(): url = tab.url name = await tab.querySelectorEval('h2','node => node.innerText') categories = await tab.querySelectorAllEval('.categories button span','nodes => nodes.map(node => node.innerText)') cover = await tab.querySelectorEval('.cover','node => node.src') score = await tab.querySelectorEval('.score','node => node.innerText') drama = await tab.querySelectorEval('.drama p','node => node.innerText') return { 'url':url, 'name':name, 'categories':categories, 'cover':cover, 'score':score, 'drama':drama } # 将提取结果作为一个字典返回 # URL:直接调用tab对象的url属性即可获取当前页面的URL # 名称:由于名称只涉及一个节点,因此我们调用querySelectorEval方法,第一个参数h2代表根据电影名称提取对应的节点;第二个参数pageFunction,这里调用node的innerText属性,提取了文本值,即电影名称。 # 类别:类别有多个,因此调用querySelectorAllEval方法。其CSS选择器.categories button span,可以选中多个类别节点;第二个参数与上相似。 # 封面:同上 # 分数:同上 # 简介:同上

在main方法里面添加对其的调用即可:

import asyncio async def main(): await init() try: for page in range(1,TOTAL_PAGE + 1): await scrape_index(page) detail_urls = await parse_index() for detail_url in detail_urls: await scrape_detail(detail_url) detail_data = await parse_detail() await save_data(detail_data) logging.info('detail_urls %s',detail_data) finally: await browser.close() if __name__ == '__main__': asyncio.get_event_loop().run_until_complete(main()) 6、数据存储

定义一个数据存储的方法,将爬取下来的数据保存为JSON格式:

# 存储数据 import json from os import makedirs from os.path import exists RESULTS_DIR = 'results' exists(RESULTS_DIR) or makedirs(RESULTS_DIR) async def save_data(data): name = data.get('name') data_path = f'{RESULTS_DIR}/{name}.json' json.dump(data,open(data_path,'a',encoding='utf-8'),ensure_ascii=False,indent=2)


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3